#include "exif.h"
#include <algorithm>
#include <cstdint>
#include <stdio.h>
#include <vector>

using std::string;

namespace {
    struct Rational {
        uint32_t numerator, denominator;
        operator double() const {
            if (denominator < 1e-20) {
                return 0;
            }
            return static_cast<double>(numerator) / static_cast<double>(denominator);
        }
    };

    // IF Entry
    class IFEntry {
    public:
        using byte_vector = std::vector<uint8_t>;
        using ascii_vector = std::string;
        using short_vector = std::vector<uint16_t>;
        using long_vector = std::vector<uint32_t>;
        using rational_vector = std::vector<Rational>;

        IFEntry()
            : tag_(0xFF), format_(0xFF), data_(0), length_(0), val_byte_(nullptr) {}
        IFEntry(const IFEntry &) = delete;
        IFEntry &operator=(const IFEntry &) = delete;
        IFEntry(IFEntry &&other)
            : tag_(other.tag_),
              format_(other.format_),
              data_(other.data_),
              length_(other.length_),
              val_byte_(other.val_byte_) {
            other.tag_ = 0xFF;
            other.format_ = 0xFF;
            other.data_ = 0;
            other.length_ = 0;
            other.val_byte_ = nullptr;
        }
        ~IFEntry() {
            delete_union();
        }
        unsigned short tag() const {
            return tag_;
        }
        void tag(unsigned short tag) {
            tag_ = tag;
        }
        unsigned short format() const {
            return format_;
        }
        bool format(unsigned short format) {
            switch (format) {
            case 0x01:
            case 0x02:
            case 0x03:
            case 0x04:
            case 0x05:
            case 0x07:
            case 0x09:
            case 0x0a:
            case 0xff:
                break;
            default:
                return false;
            }
            delete_union();
            format_ = format;
            new_union();
            return true;
        }
        unsigned data() const {
            return data_;
        }
        void data(unsigned data) {
            data_ = data;
        }
        unsigned length() const {
            return length_;
        }
        void length(unsigned length) {
            length_ = length;
        }

        // functions to access the data
        //
        // !! it's CALLER responsibility to check that format !!
        // !! is correct before accessing it's field          !!
        //
        // - getters are use here to allow future addition
        //   of checks if format is correct
        byte_vector &val_byte() {
            return *val_byte_;
        }
        ascii_vector &val_string() {
            return *val_string_;
        }
        short_vector &val_short() {
            return *val_short_;
        }
        long_vector &val_long() {
            return *val_long_;
        }
        rational_vector &val_rational() {
            return *val_rational_;
        }

    private:
        // Raw fields
        unsigned short tag_;
        unsigned short format_;
        unsigned data_;
        unsigned length_;

        // Parsed fields
        union {
            byte_vector *val_byte_;
            ascii_vector *val_string_;
            short_vector *val_short_;
            long_vector *val_long_;
            rational_vector *val_rational_;
        };

        void delete_union() {
            switch (format_) {
            case 0x1:
                delete val_byte_;
                val_byte_ = nullptr;
                break;
            case 0x2:
                delete val_string_;
                val_string_ = nullptr;
                break;
            case 0x3:
                delete val_short_;
                val_short_ = nullptr;
                break;
            case 0x4:
                delete val_long_;
                val_long_ = nullptr;
                break;
            case 0x5:
                delete val_rational_;
                val_rational_ = nullptr;
                break;
            case 0xff:
                break;
            default:
                // should not get here
                // should I throw an exception or ...?
                break;
            }
        }
        void new_union() {
            switch (format_) {
            case 0x1:
                val_byte_ = new byte_vector();
                break;
            case 0x2:
                val_string_ = new ascii_vector();
                break;
            case 0x3:
                val_short_ = new short_vector();
                break;
            case 0x4:
                val_long_ = new long_vector();
                break;
            case 0x5:
                val_rational_ = new rational_vector();
                break;
            case 0xff:
                break;
            default:
                // should not get here
                // should I throw an exception or ...?
                break;
            }
        }
    };

    // Helper functions
    template <typename T, bool alignIntel>
    T parse(const unsigned char *buf);

    template <>
    uint8_t parse<uint8_t, false>(const unsigned char *buf) {
        return *buf;
    }

    template <>
    uint8_t parse<uint8_t, true>(const unsigned char *buf) {
        return *buf;
    }

    template <>
    uint16_t parse<uint16_t, false>(const unsigned char *buf) {
        return (static_cast<uint16_t>(buf[0]) << 8) | buf[1];
    }

    template <>
    uint16_t parse<uint16_t, true>(const unsigned char *buf) {
        return (static_cast<uint16_t>(buf[1]) << 8) | buf[0];
    }

    template <>
    uint32_t parse<uint32_t, false>(const unsigned char *buf) {
        return (static_cast<uint32_t>(buf[0]) << 24) |
               (static_cast<uint32_t>(buf[1]) << 16) |
               (static_cast<uint32_t>(buf[2]) << 8) | buf[3];
    }

    template <>
    uint32_t parse<uint32_t, true>(const unsigned char *buf) {
        return (static_cast<uint32_t>(buf[3]) << 24) |
               (static_cast<uint32_t>(buf[2]) << 16) |
               (static_cast<uint32_t>(buf[1]) << 8) | buf[0];
    }

    template <>
    Rational parse<Rational, true>(const unsigned char *buf) {
        Rational r;
        r.numerator = parse<uint32_t, true>(buf);
        r.denominator = parse<uint32_t, true>(buf + 4);
        return r;
    }

    template <>
    Rational parse<Rational, false>(const unsigned char *buf) {
        Rational r;
        r.numerator = parse<uint32_t, false>(buf);
        r.denominator = parse<uint32_t, false>(buf + 4);
        return r;
    }

    /**
     * Try to read entry.length() values for this entry.
     *
     * Returns:
     *  true  - entry.length() values were read
     *  false - something went wrong, vec's content was not touched
     */
    template <typename T, bool alignIntel, typename C>
    bool extract_values(C &container, const unsigned char *buf, const unsigned base,
                        const unsigned len, const IFEntry &entry) {
        const unsigned char *data;
        uint32_t reversed_data;
        // if data fits into 4 bytes, they are stored directly in
        // the data field in IFEntry
        if (sizeof(T) * entry.length() <= 4) {
            if (alignIntel) {
                reversed_data = entry.data();
            } else {
                reversed_data = entry.data();
                // this reversing works, but is ugly
                unsigned char *data = reinterpret_cast<unsigned char *>(&reversed_data);
                unsigned char tmp;
                tmp = data[0];
                data[0] = data[3];
                data[3] = tmp;
                tmp = data[1];
                data[1] = data[2];
                data[2] = tmp;
            }
            data = reinterpret_cast<const unsigned char *>(&(reversed_data));
        } else {
            data = buf + base + entry.data();
            if (data + sizeof(T) * entry.length() > buf + len) {
                return false;
            }
        }
        container.resize(entry.length());
        for (size_t i = 0; i < entry.length(); ++i) {
            container[i] = parse<T, alignIntel>(data + sizeof(T) * i);
        }
        return true;
    }

    template <bool alignIntel>
    void parseIFEntryHeader(const unsigned char *buf, unsigned short &tag,
                            unsigned short &format, unsigned &length,
                            unsigned &data) {
        // Each directory entry is composed of:
        // 2 bytes: tag number (data field)
        // 2 bytes: data format
        // 4 bytes: number of components
        // 4 bytes: data value or offset to data value
        tag = parse<uint16_t, alignIntel>(buf);
        format = parse<uint16_t, alignIntel>(buf + 2);
        length = parse<uint32_t, alignIntel>(buf + 4);
        data = parse<uint32_t, alignIntel>(buf + 8);
    }

    template <bool alignIntel>
    void parseIFEntryHeader(const unsigned char *buf, IFEntry &result) {
        unsigned short tag;
        unsigned short format;
        unsigned length;
        unsigned data;

        parseIFEntryHeader<alignIntel>(buf, tag, format, length, data);

        result.tag(tag);
        result.format(format);
        result.length(length);
        result.data(data);
    }

    template <bool alignIntel>
    IFEntry parseIFEntry_temp(const unsigned char *buf, const unsigned offs,
                              const unsigned base, const unsigned len) {
        IFEntry result;

        // check if there even is enough data for IFEntry in the buffer
        if (buf + offs + 12 > buf + len) {
            result.tag(0xFF);
            return result;
        }

        parseIFEntryHeader<alignIntel>(buf + offs, result);

        // Parse value in specified format
        switch (result.format()) {
        case 1:
            if (!extract_values<uint8_t, alignIntel>(result.val_byte(), buf, base,
                    len, result)) {
                result.tag(0xFF);
            }
            break;
        case 2:
            // string is basically sequence of uint8_t (well, according to EXIF even
            // uint7_t, but
            // we don't have that), so just read it as bytes
            if (!extract_values<uint8_t, alignIntel>(result.val_string(), buf, base,
                    len, result)) {
                result.tag(0xFF);
            }
            // and cut zero byte at the end, since we don't want that in the
            // std::string
			if (result.val_string()=="")
			{
				break;
			}
            if (result.val_string()[result.val_string().length() - 1] == '\0') {
                result.val_string().resize(result.val_string().length() - 1);
            }
            break;
        case 3:
            if (!extract_values<uint16_t, alignIntel>(result.val_short(), buf, base,
                    len, result)) {
                result.tag(0xFF);
            }
            break;
        case 4:
            if (!extract_values<uint32_t, alignIntel>(result.val_long(), buf, base,
                    len, result)) {
                result.tag(0xFF);
            }
            break;
        case 5:
            if (!extract_values<Rational, alignIntel>(result.val_rational(), buf,
                    base, len, result)) {
                result.tag(0xFF);
            }
            break;
        case 7:
        case 9:
        case 10:
            break;
        default:
            result.tag(0xFF);
        }
        return result;
    }

    // helper functions for convinience
    template <typename T>
    T parse_value(const unsigned char *buf, bool alignIntel) {
        if (alignIntel) {
            return parse<T, true>(buf);
        } else {
            return parse<T, false>(buf);
        }
    }

    void parseIFEntryHeader(const unsigned char *buf, bool alignIntel,
                            unsigned short &tag, unsigned short &format,
                            unsigned &length, unsigned &data) {
        if (alignIntel) {
            parseIFEntryHeader<true>(buf, tag, format, length, data);
        } else {
            parseIFEntryHeader<false>(buf, tag, format, length, data);
        }
    }

    IFEntry parseIFEntry(const unsigned char *buf, const unsigned offs,
                         const bool alignIntel, const unsigned base,
                         const unsigned len) {
        if (alignIntel) {
            return parseIFEntry_temp<true>(buf, offs, base, len);
        } else {
            return parseIFEntry_temp<false>(buf, offs, base, len);
        }
    }
}

//
// Locates the EXIF segment and parses it using parseFromEXIFSegment
//
int easyexif::EXIFInfo::parseFrom(const unsigned char *buf, unsigned len) {
    // Sanity check: all JPEG files start with 0xFFD8.
    if (!buf || len < 4) return PARSE_EXIF_ERROR_NO_JPEG;
    if (buf[0] != 0xFF || buf[1] != 0xD8) return PARSE_EXIF_ERROR_NO_JPEG;

    // Sanity check: some cameras pad the JPEG image with some bytes at the end.
    // Normally, we should be able to find the JPEG end marker 0xFFD9 at the end
    // of the image buffer, but not always. As long as there are some bytes
    // except 0xD9 at the end of the image buffer, keep decrementing len until
    // an 0xFFD9 is found. If JPEG end marker 0xFFD9 is not found,
    // then we can be reasonably sure that the buffer is not a JPEG.
    while (len > 2) {
        if (buf[len - 1] == 0xD9 && buf[len - 2] == 0xFF)
            break;
        len--;
    }
    if (len <= 2)
        return PARSE_EXIF_ERROR_NO_JPEG;

    clear();

    // Scan for EXIF header (bytes 0xFF 0xE1) and do a sanity check by
    // looking for bytes "Exif\0\0". The marker length data is in Motorola
    // byte order, which results in the 'false' parameter to parse16().
    // The marker has to contain at least the TIFF header, otherwise the
    // EXIF data is corrupt. So the minimum length specified here has to be:
    //   2 bytes: section size
    //   6 bytes: "Exif\0\0" string
    //   2 bytes: TIFF header (either "II" or "MM" string)
    //   2 bytes: TIFF magic (short 0x2a00 in Motorola byte order)
    //   4 bytes: Offset to first IFD
    // =========
    //  16 bytes
    unsigned offs = 0;  // current offset into buffer
    for (offs = 0; offs < len - 1; offs++)
        if (buf[offs] == 0xFF && buf[offs + 1] == 0xE1) break;
    if (offs + 4 > len) return PARSE_EXIF_ERROR_NO_EXIF;
    offs += 2;
    unsigned short section_length = parse_value<uint16_t>(buf + offs, false);
    if (offs + section_length > len || section_length < 16)
        return PARSE_EXIF_ERROR_CORRUPT;
    offs += 2;

    return parseFromEXIFSegment(buf + offs, len - offs);
}

int easyexif::EXIFInfo::parseFrom(const string &data) {
    return parseFrom(
               reinterpret_cast<const unsigned char *>(data.data()), static_cast<unsigned>(data.length()));
}

//
// Main parsing function for an EXIF segment.
//
// PARAM: 'buf' start of the EXIF TIFF, which must be the bytes "Exif\0\0".
// PARAM: 'len' length of buffer
//
int easyexif::EXIFInfo::parseFromEXIFSegment(const unsigned char *buf,
        unsigned len) {
    bool alignIntel = true;  // byte alignment (defined in EXIF header)
    unsigned offs = 0;       // current offset into buffer
    if (!buf || len < 6) return PARSE_EXIF_ERROR_NO_EXIF;

    if (!std::equal(buf, buf + 6, "Exif\0\0")) return PARSE_EXIF_ERROR_NO_EXIF;
    offs += 6;

    // Now parsing the TIFF header. The first two bytes are either "II" or
    // "MM" for Intel or Motorola byte alignment. Sanity check by parsing
    // the unsigned short that follows, making sure it equals 0x2a. The
    // last 4 bytes are an offset into the first IFD, which are added to
    // the global offset counter. For this block, we expect the following
    // minimum size:
    //  2 bytes: 'II' or 'MM'
    //  2 bytes: 0x002a
    //  4 bytes: offset to first IDF
    // -----------------------------
    //  8 bytes
    if (offs + 8 > len) return PARSE_EXIF_ERROR_CORRUPT;
    unsigned tiff_header_start = offs;
    if (buf[offs] == 'I' && buf[offs + 1] == 'I')
        alignIntel = true;
    else {
        if (buf[offs] == 'M' && buf[offs + 1] == 'M')
            alignIntel = false;
        else
            return PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN;
    }
    this->ByteAlign = alignIntel;
    offs += 2;
    if (0x2a != parse_value<uint16_t>(buf + offs, alignIntel))
        return PARSE_EXIF_ERROR_CORRUPT;
    offs += 2;
    unsigned first_ifd_offset = parse_value<uint32_t>(buf + offs, alignIntel);
    offs += first_ifd_offset - 4;
    if (offs >= len) return PARSE_EXIF_ERROR_CORRUPT;

    // Now parsing the first Image File Directory (IFD0, for the main image).
    // An IFD consists of a variable number of 12-byte directory entries. The
    // first two bytes of the IFD section contain the number of directory
    // entries in the section. The last 4 bytes of the IFD contain an offset
    // to the next IFD, which means this IFD must contain exactly 6 + 12 * num
    // bytes of data.
    if (offs + 2 > len) return PARSE_EXIF_ERROR_CORRUPT;
    int num_entries = parse_value<uint16_t>(buf + offs, alignIntel);
    if (offs + 6 + 12 * num_entries > len) return PARSE_EXIF_ERROR_CORRUPT;
    offs += 2;
    unsigned exif_sub_ifd_offset = len;
    unsigned gps_sub_ifd_offset = len;
    while (--num_entries >= 0) {
        IFEntry result =
            parseIFEntry(buf, offs, alignIntel, tiff_header_start, len);
        offs += 12;
        switch (result.tag()) {
        case 0x102:
            // Bits per sample
            if (result.format() == 3 && result.val_short().size())
                this->BitsPerSample = result.val_short().front();
            break;

        case 0x10E:
            // Image description
            if (result.format() == 2) this->ImageDescription = result.val_string();
            break;

        case 0x10F:
            // Digicam make
            if (result.format() == 2) this->Make = result.val_string();
            break;

        case 0x110:
            // Digicam model
            if (result.format() == 2) this->Model = result.val_string();
            break;

        case 0x112:
            // Orientation of image
            if (result.format() == 3 && result.val_short().size())
                this->Orientation = result.val_short().front();
            break;

        case 0x131:
            // Software used for image
            if (result.format() == 2) this->Software = result.val_string();
            break;

        case 0x132:
            // EXIF/TIFF date/time of image modification
            if (result.format() == 2) this->DateTime = result.val_string();
            break;

        case 0x8298:
            // Copyright information
            if (result.format() == 2) this->Copyright = result.val_string();
            break;

        case 0x8825:
            // GPS IFS offset
            gps_sub_ifd_offset = tiff_header_start + result.data();
            break;

        case 0x8769:
            // EXIF SubIFD offset
            exif_sub_ifd_offset = tiff_header_start + result.data();
            break;
        }
    }

    // Jump to the EXIF SubIFD if it exists and parse all the information
    // there. Note that it's possible that the EXIF SubIFD doesn't exist.
    // The EXIF SubIFD contains most of the interesting information that a
    // typical user might want.
    if (exif_sub_ifd_offset + 4 <= len) {
        offs = exif_sub_ifd_offset;
        int num_entries = parse_value<uint16_t>(buf + offs, alignIntel);
        if (offs + 6 + 12 * num_entries > len) return PARSE_EXIF_ERROR_CORRUPT;
        offs += 2;
        while (--num_entries >= 0) {
            IFEntry result =
                parseIFEntry(buf, offs, alignIntel, tiff_header_start, len);
            switch (result.tag()) {
            case 0x829a:
                // Exposure time in seconds
                if (result.format() == 5 && result.val_rational().size())
                    this->ExposureTime = result.val_rational().front();
                break;

            case 0x829d:
                // FNumber
                if (result.format() == 5 && result.val_rational().size())
                    this->FNumber = result.val_rational().front();
                break;

            case 0x8822:
                // Exposure Program
                if (result.format() == 3 && result.val_short().size())
                    this->ExposureProgram = result.val_short().front();
                break;

            case 0x8827:
                // ISO Speed Rating
                if (result.format() == 3 && result.val_short().size())
                    this->ISOSpeedRatings = result.val_short().front();
                break;

            case 0x9003:
                // Original date and time
                if (result.format() == 2)
                    this->DateTimeOriginal = result.val_string();
                break;

            case 0x9004:
                // Digitization date and time
                if (result.format() == 2)
                    this->DateTimeDigitized = result.val_string();
                break;

            case 0x9201:
                // Shutter speed value
                if (result.format() == 5 && result.val_rational().size())
                    this->ShutterSpeedValue = result.val_rational().front();
                break;

            case 0x9204:
                // Exposure bias value
                if (result.format() == 5 && result.val_rational().size())
                    this->ExposureBiasValue = result.val_rational().front();
                break;

            case 0x9206:
                // Subject distance
                if (result.format() == 5 && result.val_rational().size())
                    this->SubjectDistance = result.val_rational().front();
                break;

            case 0x9209:
                // Flash used
                if (result.format() == 3 && result.val_short().size()) {
                    uint16_t data = result.val_short().front();

                    this->Flash = data & 1;
                    this->FlashReturnedLight = (data & 6) >> 1;
                    this->FlashMode = (data & 24) >> 3;
                }
                break;

            case 0x920a:
                // Focal length
                if (result.format() == 5 && result.val_rational().size())
                    this->FocalLength = result.val_rational().front();
                break;

            case 0x9207:
                // Metering mode
                if (result.format() == 3 && result.val_short().size())
                    this->MeteringMode = result.val_short().front();
                break;

            case 0x9291:
                // Subsecond original time
                if (result.format() == 2)
                    this->SubSecTimeOriginal = result.val_string();
                break;

            case 0xa002:
                // EXIF Image width
                if (result.format() == 4 && result.val_long().size())
                    this->ImageWidth = result.val_long().front();
                if (result.format() == 3 && result.val_short().size())
                    this->ImageWidth = result.val_short().front();
                break;

            case 0xa003:
                // EXIF Image height
                if (result.format() == 4 && result.val_long().size())
                    this->ImageHeight = result.val_long().front();
                if (result.format() == 3 && result.val_short().size())
                    this->ImageHeight = result.val_short().front();
                break;

            case 0xa20e:
                // EXIF Focal plane X-resolution
                if (result.format() == 5) {
                    this->LensInfo.FocalPlaneXResolution = result.val_rational()[0];
                }
                break;

            case 0xa20f:
                // EXIF Focal plane Y-resolution
                if (result.format() == 5) {
                    this->LensInfo.FocalPlaneYResolution = result.val_rational()[0];
                }
                break;

            case 0xa210:
                // EXIF Focal plane resolution unit
                if (result.format() == 3 && result.val_short().size()) {
                    this->LensInfo.FocalPlaneResolutionUnit = result.val_short().front();
                }
                break;

            case 0xa405:
                // Focal length in 35mm film
                if (result.format() == 3 && result.val_short().size())
                    this->FocalLengthIn35mm = result.val_short().front();
                break;

            case 0xa432:
                // Focal length and FStop.
                if (result.format() == 5) {
                    int sz = static_cast<unsigned>(result.val_rational().size());
                    if (sz)
                        this->LensInfo.FocalLengthMin = result.val_rational()[0];
                    if (sz > 1)
                        this->LensInfo.FocalLengthMax = result.val_rational()[1];
                    if (sz > 2)
                        this->LensInfo.FStopMin = result.val_rational()[2];
                    if (sz > 3)
                        this->LensInfo.FStopMax = result.val_rational()[3];
                }
                break;

            case 0xa433:
                // Lens make.
                if (result.format() == 2) {
                    this->LensInfo.Make = result.val_string();
                }
                break;

            case 0xa434:
                // Lens model.
                if (result.format() == 2) {
                    this->LensInfo.Model = result.val_string();
                }
                break;
            }
            offs += 12;
        }
    }

    // Jump to the GPS SubIFD if it exists and parse all the information
    // there. Note that it's possible that the GPS SubIFD doesn't exist.
    if (gps_sub_ifd_offset + 4 <= len) {
        offs = gps_sub_ifd_offset;
        int num_entries = parse_value<uint16_t>(buf + offs, alignIntel);
        if (offs + 6 + 12 * num_entries > len) return PARSE_EXIF_ERROR_CORRUPT;
        offs += 2;
        while (--num_entries >= 0) {
            unsigned short tag, format;
            unsigned length, data;
            parseIFEntryHeader(buf + offs, alignIntel, tag, format, length, data);
            switch (tag) {
            case 1:
                // GPS north or south
                this->GeoLocation.LatComponents.direction = *(buf + offs + 8);
                if (this->GeoLocation.LatComponents.direction == 0) {
                    this->GeoLocation.LatComponents.direction = '?';
                }
                if ('S' == this->GeoLocation.LatComponents.direction) {
                    this->GeoLocation.Latitude = -this->GeoLocation.Latitude;
                }
                break;

            case 2:
                // GPS latitude
                if ((format == 5 || format == 10) && length == 3) {
                    this->GeoLocation.LatComponents.degrees = parse_value<Rational>(
                                buf + data + tiff_header_start, alignIntel);
                    this->GeoLocation.LatComponents.minutes = parse_value<Rational>(
                                buf + data + tiff_header_start + 8, alignIntel);
                    this->GeoLocation.LatComponents.seconds = parse_value<Rational>(
                                buf + data + tiff_header_start + 16, alignIntel);
                    this->GeoLocation.Latitude =
                        this->GeoLocation.LatComponents.degrees +
                        this->GeoLocation.LatComponents.minutes / 60 +
                        this->GeoLocation.LatComponents.seconds / 3600;
                    if ('S' == this->GeoLocation.LatComponents.direction) {
                        this->GeoLocation.Latitude = -this->GeoLocation.Latitude;
                    }
                }
                break;

            case 3:
                // GPS east or west
                this->GeoLocation.LonComponents.direction = *(buf + offs + 8);
                if (this->GeoLocation.LonComponents.direction == 0) {
                    this->GeoLocation.LonComponents.direction = '?';
                }
                if ('W' == this->GeoLocation.LonComponents.direction) {
                    this->GeoLocation.Longitude = -this->GeoLocation.Longitude;
                }
                break;

            case 4:
                // GPS longitude
                if ((format == 5 || format == 10) && length == 3) {
                    this->GeoLocation.LonComponents.degrees = parse_value<Rational>(
                                buf + data + tiff_header_start, alignIntel);
                    this->GeoLocation.LonComponents.minutes = parse_value<Rational>(
                                buf + data + tiff_header_start + 8, alignIntel);
                    this->GeoLocation.LonComponents.seconds = parse_value<Rational>(
                                buf + data + tiff_header_start + 16, alignIntel);
                    this->GeoLocation.Longitude =
                        this->GeoLocation.LonComponents.degrees +
                        this->GeoLocation.LonComponents.minutes / 60 +
                        this->GeoLocation.LonComponents.seconds / 3600;
                    if ('W' == this->GeoLocation.LonComponents.direction)
                        this->GeoLocation.Longitude = -this->GeoLocation.Longitude;
                }
                break;

            case 5:
                // GPS altitude reference (below or above sea level)
                this->GeoLocation.AltitudeRef = *(buf + offs + 8);
                if (1 == this->GeoLocation.AltitudeRef) {
                    this->GeoLocation.Altitude = -this->GeoLocation.Altitude;
                }
                break;

            case 6:
                // GPS altitude
                if ((format == 5 || format == 10)) {
                    this->GeoLocation.Altitude = parse_value<Rational>(
                                                     buf + data + tiff_header_start, alignIntel);
                    if (1 == this->GeoLocation.AltitudeRef) {
                        this->GeoLocation.Altitude = -this->GeoLocation.Altitude;
                    }
                }
                break;

            case 11:
                // GPS degree of precision (DOP)
                if ((format == 5 || format == 10)) {
                    this->GeoLocation.DOP = parse_value<Rational>(
                                                buf + data + tiff_header_start, alignIntel);
                }
                break;
            }
            offs += 12;
        }
    }

    return PARSE_EXIF_SUCCESS;
}

void easyexif::EXIFInfo::clear() {
    // Strings
    ImageDescription = "";
    Make = "";
    Model = "";
    Software = "";
    DateTime = "";
    DateTimeOriginal = "";
    DateTimeDigitized = "";
    SubSecTimeOriginal = "";
    Copyright = "";

    // Shorts / unsigned / double
    ByteAlign = 0;
    Orientation = 0;

    BitsPerSample = 0;
    ExposureTime = 0;
    FNumber = 0;
    ExposureProgram = 0;
    ISOSpeedRatings = 0;
    ShutterSpeedValue = 0;
    ExposureBiasValue = 0;
    SubjectDistance = 0;
    FocalLength = 0;
    FocalLengthIn35mm = 0;
    Flash = 0;
    FlashReturnedLight = 0;
    FlashMode = 0;
    MeteringMode = 0;
    ImageWidth = 0;
    ImageHeight = 0;

    // Geolocation
    GeoLocation.Latitude = 0;
    GeoLocation.Longitude = 0;
    GeoLocation.Altitude = 0;
    GeoLocation.AltitudeRef = 0;
    GeoLocation.DOP = 0;
    GeoLocation.LatComponents.degrees = 0;
    GeoLocation.LatComponents.minutes = 0;
    GeoLocation.LatComponents.seconds = 0;
    GeoLocation.LatComponents.direction = '?';
    GeoLocation.LonComponents.degrees = 0;
    GeoLocation.LonComponents.minutes = 0;
    GeoLocation.LonComponents.seconds = 0;
    GeoLocation.LonComponents.direction = '?';

    // LensInfo
    LensInfo.FocalLengthMax = 0;
    LensInfo.FocalLengthMin = 0;
    LensInfo.FStopMax = 0;
    LensInfo.FStopMin = 0;
    LensInfo.FocalPlaneYResolution = 0;
    LensInfo.FocalPlaneXResolution = 0;
    LensInfo.FocalPlaneResolutionUnit = 0;
    LensInfo.Make = "";
    LensInfo.Model = "";
}
